package com.gitee.wsl.android.ext.base

import android.content.res.ColorStateList
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import com.gitee.wsl.android.ext.ApplicationInit.application
import com.gitee.wsl.ext.base.ColorExt
import com.gitee.wsl.ext.base.HSVSpec
import com.gitee.wsl.ext.base.StateElement
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.roundToInt

/**
 * Returns a color associated with a particular resource ID.
 *
 * @param id The desired resource identifier.
 * @return a color associated with a particular resource ID
 */
fun ColorExt.getColor(@ColorRes id: Int): Int {
    return ContextCompat.getColor(application, id)
}

/**
 * Set the alpha component of `color` to be `alpha`.
 *
 * @param color The color.
 * @param alpha Alpha component \([0..255]\) of the color.
 * @return the `color` with `alpha` component
 */
fun ColorExt.setAlphaComponent(
    @ColorInt color: Int,
    @IntRange(from = 0x0, to = 0xFF) alpha: Int
): Int {
    return color and 0x00ffffff or (alpha shl 24)
}

/**
 * Set the alpha component of `color` to be `alpha`.
 *
 * @param color The color.
 * @param alpha Alpha component \([0..1]\) of the color.
 * @return the `color` with `alpha` component
 */
fun ColorExt.setAlphaComponent(
    @ColorInt color: Int,
    @FloatRange(from = 0.0, to = 1.0) alpha: Float
): Int {
    return color and 0x00ffffff or ((alpha * 255.0f + 0.5f).toInt() shl 24)
}

/**
 * Set the red component of `color` to be `red`.
 *
 * @param color The color.
 * @param red   Red component \([0..255]\) of the color.
 * @return the `color` with `red` component
 */
fun ColorExt.setRedComponent(
    @ColorInt color: Int,
    @IntRange(from = 0x0, to = 0xFF) red: Int
): Int {
    return color and -0xff0001 or (red shl 16)
}

/**
 * Set the red component of `color` to be `red`.
 *
 * @param color The color.
 * @param red   Red component \([0..1]\) of the color.
 * @return the `color` with `red` component
 */
fun ColorExt.setRedComponent(
    @ColorInt color: Int,
    @FloatRange(from = 0.0, to = 1.0) red: Float
): Int {
    return color and -0xff0001 or ((red * 255.0f + 0.5f).toInt() shl 16)
}

/**
 * Set the green component of `color` to be `green`.
 *
 * @param color The color.
 * @param green Green component \([0..255]\) of the color.
 * @return the `color` with `green` component
 */
fun ColorExt.setGreenComponent(
    @ColorInt color: Int,
    @IntRange(from = 0x0, to = 0xFF) green: Int
): Int {
    return color and -0xff01 or (green shl 8)
}

/**
 * Set the green component of `color` to be `green`.
 *
 * @param color The color.
 * @param green Green component \([0..1]\) of the color.
 * @return the `color` with `green` component
 */
fun ColorExt.setGreenComponent(
    @ColorInt color: Int,
    @FloatRange(from = 0.0, to = 1.0) green: Float
): Int {
    return color and -0xff01 or ((green * 255.0f + 0.5f).toInt() shl 8)
}

/**
 * Set the blue component of `color` to be `blue`.
 *
 * @param color The color.
 * @param blue  Blue component \([0..255]\) of the color.
 * @return the `color` with `blue` component
 */
fun ColorExt.setBlueComponent(
    @ColorInt color: Int,
    @IntRange(from = 0x0, to = 0xFF) blue: Int
): Int {
    return color and -0x100 or blue
}

/**
 * Set the blue component of `color` to be `blue`.
 *
 * @param color The color.
 * @param blue  Blue component \([0..1]\) of the color.
 * @return the `color` with `blue` component
 */
fun ColorExt.setBlueComponent(
    @ColorInt color: Int,
    @FloatRange(from = 0.0, to = 1.0) blue: Float
): Int {
    return color and -0x100 or (blue * 255.0f + 0.5f).toInt()
}

/**
 * Color-string to color-int.
 *
 * Supported formats are:
 *
 *
 *  * `#RRGGBB`
 *  * `#AARRGGBB`
 *
 *
 *
 * The following names are also accepted: `red`, `blue`,
 * `green`, `black`, `white`, `gray`,
 * `cyan`, `magenta`, `yellow`, `lightgray`,
 * `darkgray`, `grey`, `lightgrey`, `darkgrey`,
 * `aqua`, `fuchsia`, `lime`, `maroon`,
 * `navy`, `olive`, `purple`, `silver`,
 * and `teal`.
 *
 * @param colorString The color-string.
 * @return color-int
 * @throws IllegalArgumentException The string cannot be parsed.
 */
fun ColorExt.string2Int( colorString: String): Int {
    return Color.parseColor(colorString)
}



@ColorInt
fun ColorExt.adjustOpacity(color: Int, opacity: Float): Int {
    return Color.argb(
        (Color.alpha(color) * opacity).roundToInt(),
        Color.red(color),
        Color.green(color),
        Color.blue(color)
    )
}

/**
 * Return whether the color is light.
 *
 * @param color The color.
 * @return `true`: yes<br></br>`false`: no
 */
fun ColorExt.isLightColor(@ColorInt color: Int): Boolean {
    return 0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color) >= 127.5
}

fun ColorExt.isDarkColor(color: Color): Boolean = isDarkColor(color.toArgb())

fun ColorExt.isDarkColor(@ColorInt color: Int): Boolean = ColorUtils.calculateLuminance(color) <= 0.5

@ColorInt
fun ColorExt.getForegroundColorForBackgroundColor(@ColorInt color: Int): Int {
    return if (Color.TRANSPARENT == color) Color.BLACK else if (isDarkColor(color)) Color.WHITE else Color.BLACK
}

fun ColorExt.fromHex(hex: String): Color? = try {
    Color.parseColor("#$hex").toComposeColor()
} catch (ignored: Exception) {
    null
}

fun ColorExt.getLuminanace(@ColorInt color: Int): Double {
    val rgb = intArrayOf(Color.red(color), Color.green(color), Color.blue(color))
    return getSubcolorLuminance(rgb[0]) * 0.2126 + getSubcolorLuminance(rgb[1]) * 0.7152 + getSubcolorLuminance(rgb[2]) * 0.0722
}

fun ColorExt.getSubcolorLuminance(@ColorInt color: Int): Double {
    val value = color / 255.0
    return if (value <= 0.03928) value / 12.92 else ((value + 0.055) / 1.055).pow(2.4)
}

fun ColorExt.getContrastRatio(@ColorInt colorOne: Int, @ColorInt colorTwo: Int): Double {
    val lum1 = getLuminanace(colorOne)
    val lum2 = getLuminanace(colorTwo)
    val brightest = max(lum1, lum2)
    val darkest = min(lum1, lum2)
    return (brightest + 0.05) / (darkest + 0.05)
}


// blend two colors w/ specified ratio, resulting color won't have alpha channel
@ColorInt
fun ColorExt.blend(
    @ColorInt foreground: Int,
    @ColorInt background: Int,
    @FloatRange(from = 0.0, to = 1.0) ratio: Float
): Int {
    return Color.rgb(
        ((1f - ratio) * Color.red(foreground) + ratio * Color.red(background)).toInt(),
        ((1f - ratio) * Color.green(foreground) + ratio * Color.green(background)).toInt(),
        ((1f - ratio) * Color.blue(foreground) + ratio * Color.blue(background)).toInt()
    )
}

/**
 * 创建 ColorStateList
 * @param selected 选中状态
 * @param pressed  按下状态
 * @param focused  获取焦点状态
 * @param checked  已勾选状态
 * @param normal   默认状态
 * @return [ColorStateList]
 */
fun ColorExt.createColorStateList(
    @ColorInt selected: Int,
    @ColorInt pressed: Int,
    @ColorInt focused: Int,
    @ColorInt checked: Int,
    @ColorInt normal: Int
): ColorStateList {
    // 颜色值
    val colors = IntArray(5)
    colors[0] = selected
    colors[1] = pressed
    colors[2] = focused
    colors[3] = checked
    colors[4] = normal
    // 状态值
    val states = arrayOfNulls<IntArray>(5)
    states[0] = intArrayOf(android.R.attr.state_selected)
    states[1] = intArrayOf(android.R.attr.state_pressed)
    states[2] = intArrayOf(android.R.attr.state_focused)
    states[3] = intArrayOf(android.R.attr.state_checked)
    states[4] = intArrayOf()
    // 生成 ColorStateList
    return ColorStateList(states, colors)
}

fun ColorExt.buildColorStateList(vararg pairs: Pair<Int, Int>): ColorStateList {
    val stateArray = pairs.map { intArrayOf(it.first) }.toTypedArray()
    val colorArray = pairs.map { it.second }.toIntArray()
    return ColorStateList(stateArray, colorArray)
}

fun sampleColorStateList(config:ColorSelectorBuilder.()->Unit):ColorStateList{
    val builder=ColorSelectorBuilder()
    config(builder)
    return builder.build()
}

class ColorSelectorBuilder {

    var defColor: Int? = null
    private var defColorBuilder: (() -> Int)? = null

    private val builders = mutableListOf<StateBuilder>()

    fun defState(color: Int): ColorSelectorBuilder {
        defColor = color
        return this
    }

    fun defState(builder: () -> Int): ColorSelectorBuilder {
        defColorBuilder = builder
        return this
    }

    fun addState(stateElement: StateElement, color: Int): ColorSelectorBuilder {
        val stateBuilder = StateBuilder(stateElement)
        stateBuilder.color = color
        builders.add(stateBuilder)
        return this
    }

    fun addState(stateElement: StateElement, builder: () -> Int): ColorSelectorBuilder {
        val stateBuilder = StateBuilder(stateElement)
        stateBuilder.color(builder)
        builders.add(stateBuilder)
        return this
    }

    fun build(): ColorStateList {
        val stateList = mutableListOf<IntArray>()
        val colorList = mutableListOf<Int>()
        for (b in builders) {
            val color = b.color ?: b.colorBuilder?.invoke() ?: throw NullPointerException()
            stateList.add(b.stateElement.states)
            colorList.add(color)
        }
        val defColor = defColor ?: defColorBuilder?.invoke()
        if (defColor != null) {
            stateList.add(intArrayOf())
            colorList.add(defColor)
        }
        return ColorStateList(stateList.toTypedArray(), colorList.toIntArray())
    }

    class StateBuilder(val stateElement: StateElement) {
        var color: Int? = null
        internal var colorBuilder: (() -> Int)? = null

        fun color(builder: () -> Int) {
            colorBuilder = builder
        }

    }

}


fun Color.dynamicContrast(): Color {
    val pickedColor = this.toHSVSpec()

    return when {
        pickedColor.s >= 0.5f && pickedColor.v >= 0.4f -> {
            //Primary
            if (ColorExt.isDarkColor(this)) {
                lighten()
            } else {
                darken()
            }
        }
        pickedColor.s <= 0.5f && pickedColor.v >= 0.8f -> {
            //Light
            darken()
        }
        pickedColor.s >= 0.1f && pickedColor.v <= 0.6f -> {
            //Dark
            lighten()
        }
        else -> {
            if (ColorExt.isDarkColor(this)) {
                lighten()
            } else {
                darken()
            }
        }
    }
}

fun Color.lighten(): Color = this.hsv(
    s = 0.3f,
    v = 1f
)

fun Color.darken(): Color = this.hsv(
    s = 0.6f,
    v = 0.5f
)

fun Color.toHSVSpec(): HSVSpec {
    val hsv = FloatArray(3)
    val color: Int = this.toArgb()
    Color.colorToHSV(color, hsv)
    return HSVSpec(hsv[0], hsv[1], hsv[2])
}



private fun Color.hsv(
    h: Float? = null,
    s: Float,
    v: Float
): Color {
    val hsv = FloatArray(3)
    val color: Int = this.toArgb()
    Color.colorToHSV(color, hsv)

    if (h != null) {
        hsv[0] = h
    }

    hsv[1] = s
    hsv[2] = v

    return Color.valueOf(Color.HSVToColor(hsv))
}
// endregion

fun Int.toComposeColor() = Color.valueOf(this)

// region Hex <> Color
fun Color.toHex() = Integer.toHexString(this.toArgb()).drop(2).uppercase()

val String.color: Int
    get() {
        if (this[0] != '#') {
            throw IllegalArgumentException("Can't parse $this to color")
        }
        when (length) {
            4 -> {
                val r = this[1]
                val g = this[2]
                val b = this[3]
                return Color.parseColor("#FF$r$r$g$g$b$b")
            }
            5 -> {
                val a = this[1]
                val r = this[2]
                val g = this[3]
                val b = this[4]
                return Color.parseColor("#$a$a$r$r$g$g$b$b")
            }
            7 -> {
                return Color.parseColor("#FF${substring(1)}")
            }
            9 -> {
                return Color.parseColor(this)
            }
            else ->
                throw IllegalArgumentException("Can't parse $this to color")
        }
    }